package owl2vowl.export.types;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig.Feature;
import org.semanticweb.owlapi.model.IRI;

import owl2vowl.export.JsonGeneratorVisitor;
import owl2vowl.export.JsonGeneratorVisitorImpl;
import owl2vowl.model.data.VowlData;
import owl2vowl.model.entities.AbstractEntity;
import owl2vowl.model.ontology.OntologyInformation;
import owl2vowl.model.ontology.OntologyMetric;
import owl2vowl.util.ProjectInformations;

public class JsonGenerator {
	private static final Logger logger = LogManager.getLogger(JsonGenerator.class);
	private final String VERSION_INFORMATION = "Created with OWL2VOWL (version " + ProjectInformations.getVersion()
			+ "), http://vowl.visualdataweb.org";
	private Map<String, Object> root;
	private Map<String, Object> header;
	private Map<String, Object> metrics;
	private List<Object> namespace;
	private JsonGeneratorVisitor visitor;

	public JsonGenerator() {
		this.initialize();
	}

	private void initialize() {
		this.root = new LinkedHashMap();
		this.header = new LinkedHashMap();
		this.metrics = new LinkedHashMap();
		this.namespace = new ArrayList();
		this.root.put("_comment", this.VERSION_INFORMATION);
		this.root.put("header", this.header);
		this.root.put("namespace", this.namespace);
		this.root.put("metrics", this.metrics);
		this.namespace.add(new HashMap());
	}

	public void execute(VowlData vowlData) throws Exception {
		this.processHeader(vowlData);
		this.processMetrics(vowlData);
		this.visitor = new JsonGeneratorVisitorImpl(vowlData, this.root);
		this.convertEntities(vowlData.getEntityMap());
	}

	public void export(Exporter exporter) throws Exception {
		ObjectMapper mapper = new ObjectMapper();
		mapper.configure(Feature.WRITE_NULL_MAP_VALUES, false);
		mapper.enable(new Feature[]{Feature.INDENT_OUTPUT});
		this.invoke(this.root);
		exporter.write(mapper.writeValueAsString(this.root));
	}

	protected void processHeader(VowlData vowlData) {
		OntologyInformation ontologyInformation = vowlData.getOntologyInformation();
		this.header.put("languages", vowlData.getLanguages());
		this.header.put("baseIris", vowlData.getBaseIris().stream().map(IRI::toString).collect(Collectors.toSet()));
		this.header.put("title", JsonGeneratorVisitorImpl.getLabelsFromAnnotations(ontologyInformation.getTitles()));
		this.header.put("iri", ontologyInformation.getIri());
		this.header.put("version", ontologyInformation.getVersion());
		this.header.put("author", ontologyInformation.getAuthors());
		this.header.put("description", JsonGeneratorVisitorImpl
				.getLabelsFromAnnotations(ontologyInformation.getAnnotations().getDescription()));
		this.header.put("labels",
				JsonGeneratorVisitorImpl.getLabelsFromAnnotations(ontologyInformation.getAnnotations().getLabels()));
		this.header.put("comments",
				JsonGeneratorVisitorImpl.getLabelsFromAnnotations(ontologyInformation.getAnnotations().getComments()));
		this.header.put("other", ontologyInformation.getAnnotations().getIdentifierToAnnotation());
	}

	protected <V extends AbstractEntity> void convertEntities(Map<IRI, V> entityMap) {
		Iterator arg1 = entityMap.entrySet().iterator();

		while (arg1.hasNext()) {
			Entry irivEntry = (Entry) arg1.next();
			AbstractEntity entity = (AbstractEntity) irivEntry.getValue();
			if (entity.isExportToJson()) {
				try {
					entity.accept(this.visitor);
				} catch (Exception arg7) {
					IRI key = (IRI) irivEntry.getKey();
					logger.info("WARNING! ");
					logger.info("*******************************");
					logger.info("Failed to Accept!");
					logger.info("Entity   " + entity);
					logger.info("Key      " + key);
					logger.info("Visitor  " + this.visitor);
					logger.info("Reason : " + arg7);
					Class cls = entity.getClass();
					logger.info("The type of the object is: " + cls.getName());
					logger.info("*** SKIPPING THIS ***");
				}
			}
		}

	}

	public void processMetrics(VowlData vowlData) {
		OntologyMetric metrics = vowlData.getMetrics();
		this.metrics.put("classCount", Integer.valueOf(metrics.getClasses()));
		this.metrics.put("objectPropertyCount", Integer.valueOf(metrics.getObjectProperties()));
		this.metrics.put("datatypePropertyCount", Integer.valueOf(metrics.getDatatypeProperties()));
		this.metrics.put("individualCount", Integer.valueOf(metrics.getIndividuals()));
	}

	private void invoke(Map<?, Object> jsonObj) {
		Iterator it = jsonObj.keySet().iterator();

		while (it.hasNext()) {
			Object key = it.next();
			Object o = jsonObj.get(key);
			if (o instanceof Map) {
				Map casted = (Map) o;
				if (casted.isEmpty()) {
					it.remove();
				} else {
					this.invoke(casted);
				}
			}

			if (o instanceof Collection) {
				Collection casted1 = (Collection) o;
				if (casted1.isEmpty()) {
					it.remove();
				} else {
					this.invoke(casted1);
				}
			}

			if (o instanceof String) {
				String casted2 = (String) o;
				if (casted2.isEmpty()) {
					it.remove();
				}
			}
		}

	}

	private void invoke(Collection<Object> jsonObj) {
		Iterator it = jsonObj.iterator();

		while (it.hasNext()) {
			Object o = it.next();
			if (o instanceof Map) {
				Map casted = (Map) o;
				if (casted.size() == 0) {
					it.remove();
				} else {
					this.invoke(casted);
				}
			}

			if (o instanceof Collection) {
				Collection casted1 = (Collection) o;
				if (casted1.size() == 0) {
					it.remove();
				} else {
					this.invoke(casted1);
				}
			}

			if (o instanceof String) {
				String casted2 = (String) o;
				if (casted2.isEmpty()) {
					it.remove();
				}
			}
		}

	}
}